package uy.org.softtech.cssuite.entidad.finanzas;

import static uy.org.softtech.cssuite.entidad.Entidad.FINANZAS_NAMESPACE;
import static uy.org.softtech.cssuite.helpers.HelperFechaHora.fechaLarga;
import static uy.org.softtech.cssuite.helpers.HelperFinanzas.formatearImporte;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;

import org.codehaus.jackson.annotate.JsonBackReference;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonManagedReference;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonRootName;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Index;

import uy.org.softtech.cssuite.entidad.Entidad;
import uy.org.softtech.cssuite.helpers.HelperFechaHora;
import uy.org.softtech.cssuite.helpers.HelperFinanzas;

/**
 * <p>Entidad que representa a una factura en la cuenta de cliente. También mapea la declaración del tipo de dato XML 'factura'.</p>
 * 
 * <p>El siguiente fragmento de esquema especifica el contenido esperado y representado por los atributos de esta clase.</p>
 *  
 * <pre>
 * &lt;xsd:complexType name="factura">
 *  &lt;xsd:all>
 *   &lt;xsd:element name="id" tipo="xsd:ID"/>
 *   &lt;xsd:element name="identificador" tipo="{http://cssuite.softtech.org.uy/esquemas/tipos}identificador"/>
 *   &lt;xsd:element name="estado" tipo="{http://cssuite.softtech.org.uy/esquemas/tipos}estadoFactura"/>
 *   &lt;xsd:element name="tipo" tipo="{http://cssuite.softtech.org.uy/esquemas/tipos}tipoFactura"/>
 *   &lt;xsd:element name="monto" tipo="xsd:double" default="0.00" />
 *   &lt;xsd:element name="fechaGeneracion" tipo="xsd:dateTime" />
 *   &lt;xsd:element name="fechaVencimiento" tipo="xsd:dateTime" />
 *   &lt;xsd:element name="fechaCobro" tipo="xsd:dateTime" />
 *   &lt;xsd:element name="comentarios" tipo="{http://cssuite.softtech.org.uy/esquemas/tipos}comentario" minOccurs="0"/>
 *   &lt;xsd:element name="items">
 *    &lt;xsd:complexType>
 *     &lt;xsd:sequence>
 *      &lt;xsd:element name="item" tipo="{http://cssuite.softtech.org.uy/esquemas/entidades}movimiento" minOccurs="0" maxOccurs="unbounded" />
 *     &lt;/xsd:sequence>
 *    &lt;/xsd:complexType>
 *   &lt;/xsd:element>
 *  &lt;/xsd:all>
 * &lt;/xsd:complexType>
 * 
 * </pre>
 *
 * @author fabian.lobo
 */
@Entity
@JsonRootName(value = "factura")
@JsonSerialize(include = Inclusion.NON_NULL)
@Table(name = "CSS_FACTURA")
@XmlType(name = "factura", namespace = FINANZAS_NAMESPACE, propOrder = {})
public class Factura extends Entidad implements Serializable {

	private static final long serialVersionUID = 637422145159065830L;
	
	/**
	 * Enumerado para agrupar los diferentes estados de la factura.
	 * 
	 * @author fabian.lobo
	 */
	@XmlEnum
	@XmlType(name = "estadoFactura", namespace = FINANZAS_NAMESPACE)
	public enum Estado {
		CANCELADA, GENERADA, PENDIENTE, PAGA;
	}
	
	/**
	 * Enumerado para agrupar los diferentes tipos de factura.
	 * 
	 * @author fabian.lobo
	 */
	@XmlEnum
	@XmlType(name = "tipoFactura", namespace = FINANZAS_NAMESPACE)
	public enum Tipo {
		FINAL, MENSUAL;
	}
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "ID_FACTURA")
	private Long id;
	
	@Column(name = "IDENTIFICADOR", nullable = false, length = 16, unique = true)
	@Index(name = "IDK_IDENTIFICADOR")
	private String identificador;
	
	@Column(name = "ESTADO", nullable = false, length = 20)
	@Enumerated(EnumType.STRING)
	private Estado estado;
	
	@Column(name = "TIPO_FACTURA", nullable = false, length = 20)
	@Enumerated(EnumType.STRING)
	private Tipo tipo;
	
	@Column(name = "MONTO", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	private BigDecimal monto = new BigDecimal("0.00");
	
	@Column(name = "IMPUESTOS", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	private BigDecimal impuestos = new BigDecimal("0.00");
	
	@Column(name = "DESCUENTOS", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	private BigDecimal descuentos = new BigDecimal("0.00");
	
	@Column(name = "FECHA_GENERACION", nullable = false)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	private Date fechaGeneracion;
	
	@Column(name = "FECHA_VENCIMIENTO", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	private Date fechaVencimiento;
	
	@Column(name = "FECHA_COBRO", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	private Date fechaCobro;
	
	@Column(name = "COMENTARIOS", nullable = true, length = 500)
	private String comentarios;
	
	@JsonBackReference
	@ManyToOne(optional = false, fetch = FetchType.EAGER)
	@JoinColumn(name = "ID_CUENTA", nullable = false)
	@ForeignKey(name = "FK_FACTURA_CUENTA")
	private Cuenta cuenta;
	
	@JsonManagedReference
	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "factura")
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy(value = "id")
	@XmlElementWrapper(name = "items", namespace = FINANZAS_NAMESPACE, required = true)
	@XmlElement(name = "item", namespace = FINANZAS_NAMESPACE, required = true)
	private Set<ItemFactura> items;
	
	@JsonManagedReference
	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "factura")
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy(value = "id DESC")
	@XmlElementWrapper(name = "movimientos", namespace = FINANZAS_NAMESPACE, required = true)
	@XmlElement(name = "movimiento", namespace = FINANZAS_NAMESPACE, required = true)
	private Set<Movimiento> movimientos;
	
	/*
	 *  GETTERS
	 */
	public Long getId() {
		return id;
	}
	public String getIdentificador() {
		return identificador;
	}
	public Estado getEstado() {
		return estado;
	}
	public Tipo getTipo() {
		return tipo;
	}
	public BigDecimal getMonto() {
		return monto;
	}
	public BigDecimal getImpuestos() {
		return impuestos;
	}
	public BigDecimal getDescuentos() {
		return descuentos;
	}
	public Date getFechaGeneracion() {
		return fechaGeneracion;
	}
	public Date getFechaVencimiento() {
		return fechaVencimiento;
	}
	public Date getFechaCobro() {
		return fechaCobro;
	}
	public String getComentarios() {
		return comentarios;
	}
	public Cuenta getCuenta() {
		return cuenta;
	}
	public Set<ItemFactura> getItems() {
		return items;
	}
	public Set<Movimiento> getMovimientos() {
		return movimientos;
	}

	/*
	 *  SETTERS
	 */
	public void setId(Long id) {
		this.id = id;
	}
	public void setIdentificador(String identificador) {
		this.identificador = identificador;
	}
	public void setEstado(Estado estado) {
		this.estado = estado;
	}
	public void setTipo(Tipo tipo) {
		this.tipo = tipo;
	}
	public void setMonto(BigDecimal monto) {
		this.monto = monto;
	}
	public void setImpuestos(BigDecimal impuestos) {
		this.impuestos = impuestos;
	}
	public void setDescuentos(BigDecimal descuentos) {
		this.descuentos = descuentos;
	}
	public void setFechaGeneracion(Date fechaGeneracion) {
		this.fechaGeneracion = fechaGeneracion;
	}
	public void setFechaVencimiento(Date fechaVencimiento) {
		this.fechaVencimiento = fechaVencimiento;
	}
	public void setFechaCobro(Date fechaCobro) {
		this.fechaCobro = fechaCobro;
	}
	public void setComentarios(String comentarios) {
		this.comentarios = comentarios;
	}
	public void setCuenta(Cuenta cuenta) {
		this.cuenta = cuenta;
	}
	public void setItems(Set<ItemFactura> items) {
		this.items = items;
	}
	public void setMovimientos(Set<Movimiento> movimientos) {
		this.movimientos = movimientos;
	}

	/*
	 *  CONVERSORES
	 */
	/**
	 * Convierte el importe de la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "monto")
	public String convierteMonto() {
		return formatearImporte(getMonto());
	}
	
	/**
	 * Convierte el importe de los impuestos aplicados a la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "impuestos")
	public String convierteImpuestos() {
		return formatearImporte(getImpuestos());
	}
	
	/**
	 * Convierte el importe de los descuentos aplicados a la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "descuentos")
	public String convierteDescuentos() {
		return formatearImporte(getDescuentos());
	}
	
	/**
	 * Convierte la fecha de generacion de la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaGeneracion")
	public String convierteFechaGeneracion() {
		return fechaLarga(getFechaGeneracion());
	}
	/**
	 * Convierte la fecha de vencimiento de la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	
	@JsonProperty(value = "fechaVencimiento")
	public String convierteFechaVencimiento() {
		return fechaLarga(getFechaVencimiento());
	}
	
	/**
	 * Convierte la fecha de cobro de la factura a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	
	@JsonProperty(value = "fechaCobro")
	public String convierteFechaCobro() {
		return fechaLarga(getFechaCobro());
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 10;
		result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		Factura otro = (Factura) obj;
		if (id != null && otro.id != null && id.equals(otro.id)) {
			return true;
		}
		return false;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Factura [");
		builder.append("id=" + getId() + ", ");
		builder.append("identificador=" + getIdentificador() + ", ");
		builder.append("estado=" + getEstado() + ", ");
		builder.append("tipo=" + getTipo() + ", ");
		builder.append("monto" + convierteMonto() + ", ");
		builder.append("impuestos" + convierteImpuestos() + ", ");
		builder.append("descuentos" + convierteDescuentos() + ", ");
		builder.append("fechaGeneracion" + convierteFechaGeneracion() + ", ");
		builder.append("fechaVencimiento" + convierteFechaVencimiento() + ", ");
		builder.append("fechaCobro" + convierteFechaCobro() + ", ");
		builder.append("comentarios=" + getComentarios());
		builder.append("]");
		return builder.toString();
	}

	@Override
	public String toString(boolean incluirColecciones) {
		StringBuilder result = new StringBuilder(toString());
		
		if(incluirColecciones) {
			int offset = result.length() - 2;
			StringBuilder builder = new StringBuilder(", ");
			builder.append("items=" + getItems() + ", ");
			builder.append("movimientos=" + getMovimientos());
			result.insert(offset, builder.toString());
		}	
				
		return result.toString();
	}

}
